#include <CoreFoundation/CoreFoundation.h>
#include <JavaScriptCore/JSStringRefCF.h>
#include <JavaScriptCore/JSObjectRef.h>
#include <JavaScriptCore/JavaScript.h>

#include <iostream>
#include <thread>
#include <chrono>

using namespace std;

JSGlobalContextRef globalJSContext;
JSObjectRef globalThis;
void initJSC();
void loadScript();
void jsInvoekNative();

void handleException(JSContextRef context, JSValueRef exception) {
    JSStringRef exceptionString = JSValueToStringCopy(context, exception, nullptr);
    size_t bufferSize = JSStringGetMaximumUTF8CStringSize(exceptionString);
    char* buffer = new char[bufferSize];
    JSStringGetUTF8CString(exceptionString, buffer, bufferSize);
    std::cout << "JavaScript Exception: " << buffer << std::endl;
    delete[] buffer;
    JSStringRelease(exceptionString);
    // 处理异常情况
  // 获取异常堆栈信息
    JSStringRef stackTracePropertyName = JSStringCreateWithUTF8CString("stack");
    JSValueRef stackTraceValue = JSObjectGetProperty(context, globalThis, stackTracePropertyName, nullptr);
    JSStringRelease(stackTracePropertyName);

    if (JSValueIsString(context, stackTraceValue)) {
        JSStringRef stackTraceString = JSValueToStringCopy(context, stackTraceValue, nullptr);
        size_t stackTraceBufferSize = JSStringGetMaximumUTF8CStringSize(stackTraceString);
        char* stackTraceBuffer = new char[stackTraceBufferSize];
        JSStringGetUTF8CString(stackTraceString, stackTraceBuffer, stackTraceBufferSize);
        std::cout << "JavaScript Stack Trace: " << stackTraceBuffer << std::endl;
        delete[] stackTraceBuffer;
        JSStringRelease(stackTraceString);
    }

}

JSValueRef consoleTraceCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) {
    JSStringRef stackTracePropertyName = JSStringCreateWithUTF8CString("stack");
    JSValueRef stackTraceValue = JSObjectGetProperty(context, JSContextGetGlobalObject(context), stackTracePropertyName, exception);
    JSStringRelease(stackTracePropertyName);

    if (JSValueIsString(context, stackTraceValue)) {
        JSStringRef stackTraceString = JSValueToStringCopy(context, stackTraceValue, exception);
        size_t stackTraceBufferSize = JSStringGetMaximumUTF8CStringSize(stackTraceString);
        char* stackTraceBuffer = new char[stackTraceBufferSize];
        JSStringGetUTF8CString(stackTraceString, stackTraceBuffer, stackTraceBufferSize);
        std::cout << "JavaScript Stack Trace: " << stackTraceBuffer << std::endl;
        delete[] stackTraceBuffer;
        JSStringRelease(stackTraceString);
    }
}

void initJSC() {
  JSContextGroupRef group = JSContextGroupCreate();
  globalJSContext = JSGlobalContextCreateInGroup(group, NULL);
  globalThis = JSContextGetGlobalObject(globalJSContext);

    JSStringRef consoleTraceName = JSStringCreateWithUTF8CString("consoleTrace");
    JSObjectRef consoleTraceFunction = JSObjectMakeFunctionWithCallback(globalJSContext, consoleTraceName, consoleTraceCallback);
    JSObjectSetProperty(globalJSContext, globalThis, consoleTraceName, consoleTraceFunction, kJSPropertyAttributeNone, nullptr);
    JSStringRelease(consoleTraceName);
}

void loadScript() {
  const char *script = "async function test() {\
    var s = JSON.stringify({});\
    console.log('[test] obj = ' + s);\
    var obj2 = JSON.parse(s);\
    var v = Math.trunc(Math.random() * 100).toString();\
    console.log('[v]' + v);\
    var b = await callNativeAsync({a: 1});\
    console.log('[b] = ' + b);\
    return { a }\
  }\
  test();\
  throw new Error('ccccc') \
  ";
  const char *sc2 = "var a = 1; b = a + 1;";
  const char *sc3 = "throw new Error('ccccc')";
  const char *sc4 = "function comm() {\
   try {\
    consoleTrace();\
    var x = a / b;\
    consoleTrace();\
   } catch(e) { \
    consoleTrace();\
    throw e;\
   }\
  };\
  var res;\
  try {\
     comm();\
  } catch (e) {\
    res =  e.toString() + '\\n' + e.stack;\
  }\
  comm();\
  ";
  JSStringRef scriptStrRef = JSStringCreateWithUTF8CString(sc4);
  JSValueRef exception = nullptr;
  JSValueRef ret =  JSEvaluateScript(globalJSContext, scriptStrRef, NULL, NULL, 1, &exception);
  // bool retIsNull = JSValueIsNull(globalJSContext, ret);
  // bool execptionIsNull = JSValueIsNull(globalJSContext, exception);
  // bool retIsUndefined = JSValueIsUndefined(globalJSContext, ret);
  // bool exceptionIsUndefined = JSValueIsUndefined(globalJSContext, exception);
  // bool retIsException = JSValueIsException(globalJSContext, ret);
  // if (retIsNull) {
  //   cout << "ret is null: " << exception  << endl;
  // }
  // this_thread::sleep_for(chrono::seconds(3));
  if (exception) {
    handleException(globalJSContext, exception);
  }

  // cout << ret << ", ret is null: " << retIsNull << ",exception is null: " << execptionIsNull << retIsUndefined << exceptionIsUndefined << endl;
  JSStringRelease(scriptStrRef);
}

int main(int argc, char **argv) {
  initJSC();
  loadScript();
  return 0;
}